#include "keyboard.h"
#include "print.h"
#include "interrupt.h"
#include "io.h"
#include "global.h"
#include "ioqueue.h"

struct ioqueue kbd_buf; // 键盘缓冲区

#define KBD_BUF_PORT    0x60

/* 用转义字符定义部分控制字符 */
#define esc         '\033'
#define backspace   '\b'
#define tab         '\t'
#define enter       '\r'
#define delete      '\0177'

/* 不可见字符一律定义为0 */
#define char_invisible  0
#define ctrl_l_char     char_invisible
#define ctrl_r_char     char_invisible
#define shift_l_char    char_invisible
#define shift_r_char    char_invisible
#define alt_l_char      char_invisible
#define alt_r_char      char_invisible
#define caps_lock_char  char_invisible

/* 定义控制字符的通码和断码 */
#define shift_l_make    0x2a
#define shift_r_make    0x36
#define alt_l_make      0x38
#define alt_r_make      0xe038
#define alt_r_break     0xe0b8
#define ctrl_l_make     0x1d
#define ctrl_r_make     0xe01d
#define ctrl_r_break    0xe09d
#define caps_lock_make  0x3a

/** 定义以下变量记录相应键是否按下,
 * ext_scancode用于记录makecode是否以0xe0开头 */
static bool ctrl_status, shift_status, alt_status, caps_lock_status, ext_scancode;

/* 以通码make_code为索引的二维数组 */
static char keymap[][2] = {
    /* 扫描码未与 shift 组合*/
    /*****************************/
    {0, 0},         //0x00
    {esc, esc},     //0x01
    {'1', '!'},
    {'2', '@'},
    {'3', '#'},
    {'4', '$'},
    {'5', '%'},
    {'6', '^'},
    {'7', '&'},
    {'8', '*'},       //0x09
    {'9', '('},       //0x0a
    {'0', ')'},
    {'-', '_'},
    {'=', '+'},
    {backspace, backspace},
    {tab, tab},     //0x0f
    {'q', 'Q'},     //0x10
    {'w', 'W'},     //0x11
    {'e', 'E'},
    {'r', 'R'},
    {'t', 'T'},
    {'y', 'Y'},
    {'u', 'U'},
    {'i', 'I'},
    {'o', 'O'},
    {'p', 'P'},     //0x19
    {'[', '{'},     //0x1a
    {']', '}'},
    {enter, enter},
    {ctrl_l_char, ctrl_l_char},
    {'a', 'A'},
    {'s', 'S'},     //0x1f
    {'d', 'D'},     //0x20
    {'f', 'F'},     //0x21
    {'g', 'G'},
    {'h', 'H'},
    {'j', 'J'},
    {'k', 'K'},
    {'l', 'L'},
    {';', ':'},
    {'\'', '"'},
    {'`', '~'},     //0x29
    {shift_l_char, shift_l_char},   //0x2a
    {'\\', '|'},
    {'z', 'Z'},
    {'x', 'X'},
    {'c', 'C'},
    {'v', 'V'},     //0x2f
    {'b', 'B'},     //0x30
    {'n', 'N'},
    {'m', 'M'},
    {',', '<'},
    {'.', '>'},
    {'/', '?'},
    {shift_r_char, shift_r_char},
    {'*', '*'},
    {alt_l_char, alt_l_char},
    {' ', ' '},     //0x39
    {caps_lock_char, caps_lock_char}    //0x3a
    /* 其他按键暂不处理 */
};

/* 键盘中断处理程序 */
static void intr_keyboard_handler(void) {
    
    /* 这次中断发生前的上一次中断,以下三个按键状态 */
    bool ctrl_down_last = ctrl_status;
    bool shift_down_last = shift_status;
    bool caps_lock_last = caps_lock_status;

    bool bread_code;
    uint16_t scancode = inb(KBD_BUF_PORT);

    /** 若扫描码 scancode 是 e0 开头的，表示此键的按下将产生多个扫描码，
     * 所以马上结束此次中断处理函数，等待下一个扫描码进来*/
    if (scancode == 0xe0) {
        ext_scancode = true;
        return;
    }

    /* 如果上次是以 0xe0 开头的，将扫描码合并 */
    if (ext_scancode) {
        scancode = ((0xe000) | scancode);
        ext_scancode = false;
    }

    bread_code = ((scancode & 0x0080) != 0);    //是否为断码

    if (bread_code) {
        // 是断码
        /** 由于 ctrl_r 和 alt_r 的 make_code 和 break_code 都是两字节，
         * 所以可用下面的方法取 make_code，多字节的扫描码暂不处理  */
        uint16_t make_code = (scancode &= 0xff7f);  // 得到makecode

        /* 若是任意以下三个键弹起了，将状态置为 false */
        if (make_code == ctrl_l_make || make_code == ctrl_r_make) {
            ctrl_status = false;
        } else if (make_code == shift_l_make || make_code == shift_r_make) {
            shift_status = false;
        } else if (make_code == alt_l_make || make_code == alt_r_make) {
            alt_status = false;
        } /* 由于 caps_lock 不是弹起后关闭，所以需要单独处理 */

        return; 
        // 直接返回结束此次中断处理程序
    }
    /* 若为通码，只处理数组中定义的键以及 alt_right 和 ctrl 键，全是 make_code */
    else if ((scancode > 0x00 && scancode < 0x3b) ||
             (scancode == alt_r_make) ||
             (scancode == ctrl_r_make)){
        bool shift = false; // 判断是否与 shift 组合，用来在一维数组中索引对应的字符
        if ((scancode < 0x0e) || (scancode == 0x29) ||
            (scancode == 0x1a) || (scancode == 0x1b) ||
            (scancode == 0x2b) || (scancode == 0x27) ||
            (scancode == 0x28) || (scancode == 0x33) ||
            (scancode == 0x34) || (scancode == 0x35)) {
            /****** 代表两个字母的键 ********
            0x0e 数字'0'～'9',字符'-',字符'='
            0x29 字符'`'
            0x1a 字符'['
            0x1b 字符']'
            0x2b 字符'\\'
            0x27 字符';'
            0x28 字符'\''
            0x33 字符','
            0x34 字符'.'
            0x35 字符'/'
            *******************************/
            if (shift_down_last) {
                shift = true;
            }
        } else { // 默认为字母键
            if (shift_down_last && caps_lock_last) {
                shift = false;// 如果 shift 和 capslock 同时按下
            } else if (shift_down_last || caps_lock_last) {
                shift = true;// 如果 shift 和 capslock 任意被按下
            } else {
                shift = false;
            }
        }
        uint8_t index = (scancode &= 0x00ff);// 将扫描码的高字节置 0，主要针对高字节是 e0 的扫描码

        char cur_char = keymap[index][shift]; // 在数组中找到对应的字符

        /* 只处理 ASCII 码不为 0 的键 */
        if (cur_char)
        {

            /*****************  快捷键ctrl+l和ctrl+u的处理 *********************
             * 下面是把ctrl+l和ctrl+u这两种组合键产生的字符置为:
             * cur_char的asc码-字符a的asc码, 此差值比较小,
             * 属于asc码表中不可见的字符部分.故不会产生可见字符.
             * 我们在shell中将ascii值为l-a和u-a的分别处理为清屏和删除输入的快捷键*/
            if ((ctrl_down_last && cur_char == 'l') || (ctrl_down_last && cur_char == 'u'))
            {
                cur_char -= 'a';
            }
            /****************************************************************/
            // put_char(cur_char); // 临时
            ioq_putchar(&kbd_buf, cur_char);
            return;
        }

        /* 记录本次是否按下了下面几类控制键之一，供下次键入时判断组合键 */
        if (scancode == ctrl_l_make || scancode == ctrl_r_make) {
            ctrl_status = true;
        } else if (scancode == shift_l_make || scancode == shift_r_make) {
            shift_status = true;
        } else if (scancode == alt_l_make || scancode == alt_r_make) {
            alt_status = true;
        } else if (scancode == caps_lock_make) {
            /** 不管之前是否有按下 caps_lock 键，当再次按下时则状态取反，
             * 即已经开启时，再按下同样的键是关闭。关闭时按下表示开启*/
            caps_lock_status = !caps_lock_status;
        }
    } else {
        put_str("unknown key\n");
    }
}

void keyboard_init(void) {
    put_str("keyboard init start\n");
    ioqueue_init(&kbd_buf);                             // 初始化了键盘io缓冲区
    register_handler(0x21, intr_keyboard_handler);
    put_str("keyboard init done\n");
}